/*
 * Conditions Of Use 
 * 
 * This software was developed by employees of the National Institute of
 * Standards and Technology (NIST), an agency of the Federal Government.
 * Pursuant to title 15 Untied States Code Section 105, works of NIST
 * employees are not subject to copyright protection in the United States
 * and are considered to be in the public domain.  As a result, a formal
 * license is not needed to use the software.
 * 
 * This software is provided by NIST as a service and is expressly
 * provided "AS IS."  NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED
 * OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT
 * AND DATA ACCURACY.  NIST does not warrant or make any representations
 * regarding the use of the software or the results thereof, including but
 * not limited to the correctness, accuracy, reliability or usefulness of
 * the software.
 * 
 * Permission to use this software is contingent upon your acceptance
 * of the terms of this agreement
 *  
 * .
 * 
 */
package gov.nist.javax.sip.stack;

import gov.nist.core.InternalErrorHandler;
import gov.nist.javax.sip.SIPConstants;
import gov.nist.javax.sip.SipProviderImpl;
import gov.nist.javax.sip.header.CallID;
import gov.nist.javax.sip.header.Event;
import gov.nist.javax.sip.header.From;
import gov.nist.javax.sip.header.To;
import gov.nist.javax.sip.header.Via;
import gov.nist.javax.sip.header.ViaList;
import gov.nist.javax.sip.message.SIPMessage;
import gov.nist.javax.sip.message.SIPRequest;
import gov.nist.javax.sip.message.SIPResponse;

import java.io.IOException;
import java.net.InetAddress;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

import javax.sip.Dialog;
import javax.sip.IOExceptionEvent;
import javax.sip.ServerTransaction;
import javax.sip.TransactionState;
import javax.sip.message.Request;
import javax.sip.message.Response;

/*
 * Modifications for TLS Support added by Daniel J. Martinez Manzano
 * <dani@dif.um.es> Bug fixes by Jeroen van Bemmel (JvB) and others.
 */

/**
 * Abstract class to support both client and server transactions. Provides an
 * encapsulation of a message channel, handles timer events, and creation of the
 * Via header for a message.
 * 
 * @author Jeff Keyser
 * @author M. Ranganathan
 * 
 * 
 * @version 1.2 $Revision: 1.60 $ $Date: 2009/05/11 18:52:39 $
 */
public abstract class SIPTransaction extends MessageChannel implements
		javax.sip.Transaction, gov.nist.javax.sip.TransactionExt {

	protected boolean toListener; // Flag to indicate that the listener gets

	// to see the event.

	protected int BASE_TIMER_INTERVAL = SIPTransactionStack.BASE_TIMER_INTERVAL;
	/**
	 * 5 sec Maximum duration a message will remain in the network
	 */
	protected int T4 = 5000 / BASE_TIMER_INTERVAL;

	/**
	 * The maximum retransmit interval for non-INVITE requests and INVITE
	 * responses
	 */
	protected int T2 = 4000 / BASE_TIMER_INTERVAL;
	protected int TIMER_I = T4;

	protected int TIMER_K = T4;

	protected int TIMER_D = 32000 / BASE_TIMER_INTERVAL;

	// protected static final int TIMER_C = 3 * 60 * 1000 / BASE_TIMER_INTERVAL;

	/**
	 * One timer tick.
	 */
	protected static final int T1 = 1;

	/**
	 * INVITE request retransmit interval, for UDP only
	 */
	protected static final int TIMER_A = 1;

	/**
	 * INVITE transaction timeout timer
	 */
	protected static final int TIMER_B = 64;

	protected static final int TIMER_J = 64;

	protected static final int TIMER_F = 64;

	protected static final int TIMER_H = 64;

	// Proposed feature for next release.
	protected Object applicationData;

	protected SIPResponse lastResponse;

	// private SIPDialog dialog;

	protected boolean isMapped;

	private Semaphore semaphore;

	protected boolean isSemaphoreAquired;

	// protected boolean eventPending; // indicate that an event is pending
	// here.

	protected String transactionId; // Transaction Id.

	// Audit tag used by the SIP Stack audit
	public long auditTag = 0;

	/**
	 * Initialized but no state assigned.
	 */
	public static final TransactionState INITIAL_STATE = null;

	/**
	 * Trying state.
	 */
	public static final TransactionState TRYING_STATE = TransactionState.TRYING;

	/**
	 * CALLING State.
	 */
	public static final TransactionState CALLING_STATE = TransactionState.CALLING;

	/**
	 * Proceeding state.
	 */
	public static final TransactionState PROCEEDING_STATE = TransactionState.PROCEEDING;

	/**
	 * Completed state.
	 */
	public static final TransactionState COMPLETED_STATE = TransactionState.COMPLETED;

	/**
	 * Confirmed state.
	 */
	public static final TransactionState CONFIRMED_STATE = TransactionState.CONFIRMED;

	/**
	 * Terminated state.
	 */
	public static final TransactionState TERMINATED_STATE = TransactionState.TERMINATED;

	/**
	 * Maximum number of ticks between retransmissions.
	 */
	protected static final int MAXIMUM_RETRANSMISSION_TICK_COUNT = 8;

	// Parent stack for this transaction
	protected transient SIPTransactionStack sipStack;

	// Original request that is being handled by this transaction
	protected SIPRequest originalRequest;

	// Underlying channel being used to send messages for this transaction
	private transient MessageChannel encapsulatedChannel;

	// Port of peer
	protected int peerPort;

	// Address of peer
	protected InetAddress peerInetAddress;

	// Address of peer as a string
	protected String peerAddress;

	// Protocol of peer
	protected String peerProtocol;

	// @@@ hagai - NAT changes
	// Source port extracted from peer packet
	protected int peerPacketSourcePort;

	protected InetAddress peerPacketSourceAddress;

	protected boolean transactionTimerStarted = false;

	// Transaction branch ID
	private String branch;

	// Method of the Request used to create the transaction.
	private String method;

	// Sequence number of request used to create the transaction
	private long cSeq;

	// Current transaction state
	private TransactionState currentState;

	// Number of ticks the retransmission timer was set to last
	private transient int retransmissionTimerLastTickCount;

	// Number of ticks before the message is retransmitted
	private transient int retransmissionTimerTicksLeft;

	// Number of ticks before the transaction times out
	protected int timeoutTimerTicksLeft;

	// List of event listeners for this transaction
	private transient Set<SIPTransactionEventListener> eventListeners;

	// Hang on to these - we clear out the request URI after
	// transaction goes to final state. Pointers to these are kept around
	// for transaction matching as long as the transaction is in
	// the transaction table.
	protected From from;

	protected To to;

	protected Event event;

	protected CallID callId;

	// Back ptr to the JAIN layer.
	// private Object wrapper;

	// Counter for caching of connections.
	// Connection lingers for collectionTime
	// after the Transaction goes to terminated state.
	protected int collectionTime;

	protected String toTag;

	protected String fromTag;

	private boolean terminatedEventDelivered;

	public String getBranchId() {
		return this.branch;
	}

	/**
	 * The linger timer is used to remove the transaction from the transaction
	 * table after it goes into terminated state. This allows connection caching
	 * and also takes care of race conditins.
	 * 
	 * 
	 */
	class LingerTimer extends SIPStackTimerTask {

		public LingerTimer() {
			SIPTransaction sipTransaction = SIPTransaction.this;
			if (sipStack.logWriter.isLoggingEnabled()) {
				sipStack.logWriter.logDebug("LingerTimer : "
						+ sipTransaction.getTransactionId());
			}

		}

		protected void runTask() {
			SIPTransaction transaction = SIPTransaction.this;
			// release the connection associated with this transaction.
			SIPTransactionStack sipStack = transaction.getSIPStack();

			if (sipStack.logWriter.isLoggingEnabled()) {
				sipStack.logWriter.logDebug("LingerTimer: run() : "
						+ getTransactionId());
			}

			if (transaction instanceof SIPClientTransaction) {
				sipStack.removeTransaction(transaction);
				transaction.close();

			} else if (transaction instanceof ServerTransaction) {
				// Remove it from the set
				if (sipStack.isLoggingEnabled())
					sipStack.logWriter.logDebug("removing" + transaction);
				sipStack.removeTransaction(transaction);
				if ((!sipStack.cacheServerConnections)
						&& transaction.encapsulatedChannel instanceof TCPMessageChannel
						&& --((TCPMessageChannel) transaction.encapsulatedChannel).useCount <= 0) {
					// Close the encapsulated socket if stack is configured
					transaction.close();
				} else if ((!sipStack.cacheServerConnections)
						&& transaction.encapsulatedChannel instanceof TLSMessageChannel
						&& --((TLSMessageChannel) transaction.encapsulatedChannel).useCount <= 0) {
					// Close the encapsulated socket if stack is configured
					transaction.close();
				} else {
					if (sipStack.isLoggingEnabled()
							&& (!sipStack.cacheServerConnections)
							&& transaction.isReliable()) {
						int useCount;

						if (transaction.encapsulatedChannel instanceof TCPMessageChannel)
							useCount = ((TCPMessageChannel) transaction.encapsulatedChannel).useCount;
						else
							useCount = ((TLSMessageChannel) transaction.encapsulatedChannel).useCount;

						sipStack.logWriter.logDebug("Use Count = " + useCount);
					}
				}
			}

		}
	}

	/**
	 * Transaction constructor.
	 * 
	 * @param newParentStack
	 *            Parent stack for this transaction.
	 * @param newEncapsulatedChannel
	 *            Underlying channel for this transaction.
	 */
	protected SIPTransaction(SIPTransactionStack newParentStack,
			MessageChannel newEncapsulatedChannel) {

		sipStack = newParentStack;
		this.semaphore = new Semaphore(1,true);

		encapsulatedChannel = newEncapsulatedChannel;
		// Record this to check if the address has changed before sending
		// message to avoid possible race condition.
		this.peerPort = newEncapsulatedChannel.getPeerPort();
		this.peerAddress = newEncapsulatedChannel.getPeerAddress();
		this.peerInetAddress = newEncapsulatedChannel.getPeerInetAddress();
		// @@@ hagai
		this.peerPacketSourcePort = newEncapsulatedChannel
				.getPeerPacketSourcePort();
		this.peerPacketSourceAddress = newEncapsulatedChannel
				.getPeerPacketSourceAddress();
		this.peerProtocol = newEncapsulatedChannel.getPeerProtocol();
		if (this.isReliable()) {
			if (encapsulatedChannel instanceof TLSMessageChannel) {
				((TLSMessageChannel) encapsulatedChannel).useCount++;
				if (sipStack.isLoggingEnabled())
					sipStack.logWriter
							.logDebug("use count for encapsulated channel"
									+ this
									+ " "
									+ ((TLSMessageChannel) encapsulatedChannel).useCount);
			} else {
				((TCPMessageChannel) encapsulatedChannel).useCount++;
				if (sipStack.isLoggingEnabled())
					sipStack.logWriter
							.logDebug("use count for encapsulated channel"
									+ this
									+ " "
									+ ((TCPMessageChannel) encapsulatedChannel).useCount);
			}
		}

		this.currentState = null;

		disableRetransmissionTimer();
		disableTimeoutTimer();
		eventListeners = Collections.synchronizedSet(new HashSet<SIPTransactionEventListener>());

		// Always add the parent stack as a listener
		// of this transaction
		addEventListener(newParentStack);

	}

	/**
	 * Sets the request message that this transaction handles.
	 * 
	 * @param newOriginalRequest
	 *            Request being handled.
	 */
	public void setOriginalRequest(SIPRequest newOriginalRequest) {

		// Branch value of topmost Via header
		String newBranch;

		if (this.originalRequest != null
				&& (!this.originalRequest.getTransactionId().equals(
						newOriginalRequest.getTransactionId()))) {
			sipStack.removeTransactionHash(this);
		}
		// This will be cleared later.

		this.originalRequest = newOriginalRequest;

		// just cache the control information so the
		// original request can be released later.
		this.method = newOriginalRequest.getMethod();
		this.from = (From) newOriginalRequest.getFrom();
		this.to = (To) newOriginalRequest.getTo();
		// Save these to avoid concurrent modification exceptions!
		this.toTag = this.to.getTag();
		this.fromTag = this.from.getTag();
		this.callId = (CallID) newOriginalRequest.getCallId();
		this.cSeq = newOriginalRequest.getCSeq().getSeqNumber();
		this.event = (Event) newOriginalRequest.getHeader("Event");
		this.transactionId = newOriginalRequest.getTransactionId();

		originalRequest.setTransaction(this);

		// If the message has an explicit branch value set,
		newBranch = ((Via) newOriginalRequest.getViaHeaders().getFirst())
				.getBranch();
		if (newBranch != null) {
			if (sipStack.isLoggingEnabled())
				sipStack.logWriter.logDebug("Setting Branch id : " + newBranch);

			// Override the default branch with the one
			// set by the message
			setBranch(newBranch);

		} else {
			if (sipStack.isLoggingEnabled())
				sipStack.logWriter.logDebug("Branch id is null - compute TID!"
						+ newOriginalRequest.encode());
			setBranch(newOriginalRequest.getTransactionId());
		}
	}

	/**
	 * Gets the request being handled by this transaction.
	 * 
	 * @return -- the original Request associated with this transaction.
	 */
	public SIPRequest getOriginalRequest() {
		return originalRequest;
	}

	/**
	 * Get the original request but cast to a Request structure.
	 * 
	 * @return the request that generated this transaction.
	 */
	public Request getRequest() {
		return (Request) originalRequest;
	}

	/**
	 * Returns a flag stating whether this transaction is for an INVITE request
	 * or not.
	 * 
	 * @return -- true if this is an INVITE request, false if not.
	 */
	public final boolean isInviteTransaction() {
		return getMethod().equals(Request.INVITE);
	}

	/**
	 * Return true if the transaction corresponds to a CANCEL message.
	 * 
	 * @return -- true if the transaciton is a CANCEL transaction.
	 */
	public final boolean isCancelTransaction() {
		return getMethod().equals(Request.CANCEL);
	}

	/**
	 * Return a flag that states if this is a BYE transaction.
	 * 
	 * @return true if the transaciton is a BYE transaction.
	 */
	public final boolean isByeTransaction() {
		return getMethod().equals(Request.BYE);
	}

	/**
	 * Returns the message channel used for transmitting/receiving messages for
	 * this transaction. Made public in support of JAIN dual transaction model.
	 * 
	 * @return Encapsulated MessageChannel.
	 * 
	 */
	public MessageChannel getMessageChannel() {
		return encapsulatedChannel;
	}

	/**
	 * Sets the Via header branch parameter used to identify this transaction.
	 * 
	 * @param newBranch
	 *            New string used as the branch for this transaction.
	 */
	public final void setBranch(String newBranch) {
		branch = newBranch;
	}

	/**
	 * Gets the current setting for the branch parameter of this transaction.
	 * 
	 * @return Branch parameter for this transaction.
	 */
	public final String getBranch() {
		if (this.branch == null) {
			this.branch = getOriginalRequest().getTopmostVia().getBranch();
		}
		return branch;
	}

	/**
	 * Get the method of the request used to create this transaction.
	 * 
	 * @return the method of the request for the transaction.
	 */
	public final String getMethod() {
		return this.method;
	}

	/**
	 * Get the Sequence number of the request used to create the transaction.
	 * 
	 * @return the cseq of the request used to create the transaction.
	 */
	public final long getCSeq() {
		return this.cSeq;
	}

	/**
	 * Changes the state of this transaction.
	 * 
	 * @param newState
	 *            New state of this transaction.
	 */
	public void setState(TransactionState newState) {
		// PATCH submitted by sribeyron
		if (currentState == TransactionState.COMPLETED) {
			if (newState != TransactionState.TERMINATED
					&& newState != TransactionState.CONFIRMED)
				newState = TransactionState.COMPLETED;
		}
		if (currentState == TransactionState.CONFIRMED) {
			if (newState != TransactionState.TERMINATED)
				newState = TransactionState.CONFIRMED;
		}
		if (currentState != TransactionState.TERMINATED)
			currentState = newState;
		else
			newState = currentState;
		// END OF PATCH
		if (sipStack.isLoggingEnabled()) {
			sipStack.logWriter.logDebug("Transaction:setState " + newState
					+ " " + this + " branchID = " + this.getBranch()
					+ " isClient = " + (this instanceof SIPClientTransaction));
			sipStack.logWriter.logStackTrace();
		}
	}

	/**
	 * Gets the current state of this transaction.
	 * 
	 * @return Current state of this transaction.
	 */
	public TransactionState getState() {
		return this.currentState;
	}

	/**
	 * Enables retransmission timer events for this transaction to begin in one
	 * tick.
	 */
	protected final void enableRetransmissionTimer() {
		enableRetransmissionTimer(1);
	}

	/**
	 * Enables retransmission timer events for this transaction to begin after
	 * the number of ticks passed to this routine.
	 * 
	 * @param tickCount
	 *            Number of ticks before the next retransmission timer event
	 *            occurs.
	 */
	protected final void enableRetransmissionTimer(int tickCount) {
		// For INVITE Client transactions, double interval each time
		if (isInviteTransaction() && (this instanceof SIPClientTransaction)) {
			retransmissionTimerTicksLeft = tickCount;
		} else {
			// non-INVITE transactions and 3xx-6xx responses are capped at T2
			retransmissionTimerTicksLeft = Math.min(tickCount,
					MAXIMUM_RETRANSMISSION_TICK_COUNT);
		}
		retransmissionTimerLastTickCount = retransmissionTimerTicksLeft;
	}

	/**
	 * Turns off retransmission events for this transaction.
	 */
	protected final void disableRetransmissionTimer() {
		retransmissionTimerTicksLeft = -1;
	}

	/**
	 * Enables a timeout event to occur for this transaction after the number of
	 * ticks passed to this method.
	 * 
	 * @param tickCount
	 *            Number of ticks before this transaction times out.
	 */
	protected final void enableTimeoutTimer(int tickCount) {
		if (sipStack.isLoggingEnabled())
			sipStack.logWriter.logDebug("enableTimeoutTimer " + this
					+ " tickCount " + tickCount + " currentTickCount = "
					+ timeoutTimerTicksLeft);

		timeoutTimerTicksLeft = tickCount;
	}

	/**
	 * Disabled the timeout timer.
	 */
	protected final void disableTimeoutTimer() {
		timeoutTimerTicksLeft = -1;
	}

	/**
	 * Fired after each timer tick. Checks the retransmission and timeout timers
	 * of this transaction, and fired these events if necessary.
	 */
	final void fireTimer() {
		// If the timeout timer is enabled,

		if (timeoutTimerTicksLeft != -1) {
			// Count down the timer, and if it has run out,
			if (--timeoutTimerTicksLeft == 0) {
				// Fire the timeout timer
				fireTimeoutTimer();
			}
		}

		// If the retransmission timer is enabled,
		if (retransmissionTimerTicksLeft != -1) {
			// Count down the timer, and if it has run out,
			if (--retransmissionTimerTicksLeft == 0) {
				// Enable this timer to fire again after
				// twice the original time
				enableRetransmissionTimer(retransmissionTimerLastTickCount * 2);
				// Fire the timeout timer
				fireRetransmissionTimer();
			}
		}
	}

	/**
	 * Tests if this transaction has terminated.
	 * 
	 * @return Trus if this transaction is terminated, false if not.
	 */
	public final boolean isTerminated() {
		return getState() == TERMINATED_STATE;
	}

	public String getHost() {
		return encapsulatedChannel.getHost();
	}

	public String getKey() {
		return encapsulatedChannel.getKey();
	}

	public int getPort() {
		return encapsulatedChannel.getPort();
	}

	public SIPTransactionStack getSIPStack() {
		return (SIPTransactionStack) sipStack;
	}

	public String getPeerAddress() {
		return this.peerAddress;
	}

	public int getPeerPort() {
		return this.peerPort;
	}

	// @@@ hagai
	public int getPeerPacketSourcePort() {
		return this.peerPacketSourcePort;
	}

	public InetAddress getPeerPacketSourceAddress() {
		return this.peerPacketSourceAddress;
	}

	protected InetAddress getPeerInetAddress() {
		return this.peerInetAddress;
	}

	protected String getPeerProtocol() {
		return this.peerProtocol;
	}

	public String getTransport() {
		return encapsulatedChannel.getTransport();
	}

	public boolean isReliable() {
		return encapsulatedChannel.isReliable();
	}

	/**
	 * Returns the Via header for this channel. Gets the Via header of the
	 * underlying message channel, and adds a branch parameter to it for this
	 * transaction.
	 */
	public Via getViaHeader() {
		// Via header of the encapulated channel
		Via channelViaHeader;

		// Add the branch parameter to the underlying
		// channel's Via header
		channelViaHeader = super.getViaHeader();
		try {
			channelViaHeader.setBranch(branch);
		} catch (java.text.ParseException ex) {
		}
		return channelViaHeader;

	}

	/**
	 * Process the message through the transaction and sends it to the SIP peer.
	 * 
	 * @param messageToSend
	 *            Message to send to the SIP peer.
	 */
	public void sendMessage(SIPMessage messageToSend) throws IOException {
		// Use the peer address, port and transport
		// that was specified when the transaction was
		// created. Bug was noted by Bruce Evangelder
		// soleo communications.
		try {
			encapsulatedChannel.sendMessage(messageToSend,
					this.peerInetAddress, this.peerPort);
		} finally {
			this.startTransactionTimer();
		}
	}

	/**
	 * Parse the byte array as a message, process it through the transaction,
	 * and send it to the SIP peer. This is just a placeholder method -- calling
	 * it will result in an IO exception.
	 * 
	 * @param messageBytes
	 *            Bytes of the message to send.
	 * @param receiverAddress
	 *            Address of the target peer.
	 * @param receiverPort
	 *            Network port of the target peer.
	 * 
	 * @throws IOException
	 *             If called.
	 */
	protected void sendMessage(byte[] messageBytes,
			InetAddress receiverAddress, int receiverPort, boolean retry)
			throws IOException {
		throw new IOException(
				"Cannot send unparsed message through Transaction Channel!");
	}

	/**
	 * Adds a new event listener to this transaction.
	 * 
	 * @param newListener
	 *            Listener to add.
	 */
	public void addEventListener(SIPTransactionEventListener newListener) {
		eventListeners.add(newListener);
	}

	/**
	 * Removed an event listener from this transaction.
	 * 
	 * @param oldListener
	 *            Listener to remove.
	 */
	public void removeEventListener(SIPTransactionEventListener oldListener) {
		eventListeners.remove(oldListener);
	}

	/**
	 * Creates a SIPTransactionErrorEvent and sends it to all of the listeners
	 * of this transaction. This method also flags the transaction as
	 * terminated.
	 * 
	 * @param errorEventID
	 *            ID of the error to raise.
	 */
	protected void raiseErrorEvent(int errorEventID) {

		// Error event to send to all listeners
		SIPTransactionErrorEvent newErrorEvent;
		// Iterator through the list of listeners
		Iterator<SIPTransactionEventListener> listenerIterator;
		// Next listener in the list
		SIPTransactionEventListener nextListener;

		// Create the error event
		newErrorEvent = new SIPTransactionErrorEvent(this, errorEventID);

		// Loop through all listeners of this transaction
		synchronized (eventListeners) {
			listenerIterator = eventListeners.iterator();
			while (listenerIterator.hasNext()) {
				// Send the event to the next listener
				nextListener = (SIPTransactionEventListener) listenerIterator
						.next();
				nextListener.transactionErrorEvent(newErrorEvent);
			}
		}
		// Clear the event listeners after propagating the error.
		// Retransmit notifications are just an alert to the
		// application (they are not an error).
		if (errorEventID != SIPTransactionErrorEvent.TIMEOUT_RETRANSMIT) {
			eventListeners.clear();

			// Errors always terminate a transaction
			this.setState(TransactionState.TERMINATED);

			if (this instanceof SIPServerTransaction && this.isByeTransaction()
					&& this.getDialog() != null)
				((SIPDialog) this.getDialog())
						.setState(SIPDialog.TERMINATED_STATE);
		}
	}

	/**
	 * A shortcut way of telling if we are a server transaction.
	 */
	protected boolean IsServerTransaction() {
		return this instanceof SIPServerTransaction;
	}

	/**
	 * Gets the dialog object of this Transaction object. This object returns
	 * null if no dialog exists. A dialog only exists for a transaction when a
	 * session is setup between a User Agent Client and a User Agent Server,
	 * either by a 1xx Provisional Response for an early dialog or a 200OK
	 * Response for a committed dialog.
	 * 
	 * @return the Dialog Object of this Transaction object.
	 * @see Dialog
	 */
	public abstract Dialog getDialog();

	/**
	 * set the dialog object.
	 * 
	 * @param sipDialog --
	 *            the dialog to set.
	 * @param dialogId --
	 *            the dialog id ot associate with the dialog.s
	 */
	public abstract void setDialog(SIPDialog sipDialog, String dialogId);

	/**
	 * Returns the current value of the retransmit timer in milliseconds used to
	 * retransmit messages over unreliable transports.
	 * 
	 * @return the integer value of the retransmit timer in milliseconds.
	 */
	public int getRetransmitTimer() {
		return SIPTransactionStack.BASE_TIMER_INTERVAL;
	}

	/**
	 * Get the host to assign for an outgoing Request via header.
	 */
	public String getViaHost() {
		return this.getViaHeader().getHost();

	}

	/**
	 * Get the last response. This is used internally by the implementation.
	 * Dont rely on it.
	 * 
	 * @return the last response received (for client transactions) or sent (for
	 *         server transactions).
	 */
	public SIPResponse getLastResponse() {
		return this.lastResponse;
	}

	/**
	 * Get the JAIN interface response
	 */
	public Response getResponse() {
		return (Response) this.lastResponse;
	}

	/**
	 * Get the transaction Id.
	 */
	public String getTransactionId() {
		return this.transactionId;
	}

	/**
	 * Hashcode method for fast hashtable lookup.
	 */
	public int hashCode() {
		if (this.transactionId == null)
			return -1;
		else
			return this.transactionId.hashCode();
	}

	/**
	 * Get the port to assign for the via header of an outgoing message.
	 */
	public int getViaPort() {
		return this.getViaHeader().getPort();
	}

	/**
	 * A method that can be used to test if an incoming request belongs to this
	 * transction. This does not take the transaction state into account when
	 * doing the check otherwise it is identical to isMessagePartOfTransaction.
	 * This is useful for checking if a CANCEL belongs to this transaction.
	 * 
	 * @param requestToTest
	 *            is the request to test.
	 * @return true if the the request belongs to the transaction.
	 * 
	 */
	public boolean doesCancelMatchTransaction(SIPRequest requestToTest) {

		// List of Via headers in the message to test
		ViaList viaHeaders;
		// Topmost Via header in the list
		Via topViaHeader;
		// Branch code in the topmost Via header
		String messageBranch;
		// Flags whether the select message is part of this transaction
		boolean transactionMatches;

		transactionMatches = false;

		if (this.getOriginalRequest() == null
				|| this.getOriginalRequest().getMethod().equals(Request.CANCEL))
			return false;
		// Get the topmost Via header and its branch parameter
		viaHeaders = requestToTest.getViaHeaders();
		if (viaHeaders != null) {

			topViaHeader = (Via) viaHeaders.getFirst();
			messageBranch = topViaHeader.getBranch();
			if (messageBranch != null) {

				// If the branch parameter exists but
				// does not start with the magic cookie,
				if (!messageBranch.startsWith(SIPConstants.BRANCH_MAGIC_COOKIE)) {

					// Flags this as old
					// (RFC2543-compatible) client
					// version
					messageBranch = null;

				}

			}

			// If a new branch parameter exists,
			if (messageBranch != null && this.getBranch() != null) {

				// If the branch equals the branch in
				// this message,
				if (getBranch().equalsIgnoreCase(messageBranch)
						&& topViaHeader.getSentBy().equals(
								((Via) getOriginalRequest().getViaHeaders()
										.getFirst()).getSentBy())) {
					transactionMatches = true;
					if (sipStack.isLoggingEnabled())
						sipStack.logWriter.logDebug("returning  true");
				}

			} else {
				// If this is an RFC2543-compliant message,
				// If RequestURI, To tag, From tag,
				// CallID, CSeq number, and top Via
				// headers are the same,
				if (sipStack.isLoggingEnabled())
					sipStack.logWriter.logDebug("testing against "
							+ getOriginalRequest());

				if (getOriginalRequest().getRequestURI().equals(
						requestToTest.getRequestURI())
						&& getOriginalRequest().getTo().equals(
								requestToTest.getTo())
						&& getOriginalRequest().getFrom().equals(
								requestToTest.getFrom())
						&& getOriginalRequest().getCallId().getCallId().equals(
								requestToTest.getCallId().getCallId())
						&& getOriginalRequest().getCSeq().getSeqNumber() == requestToTest
								.getCSeq().getSeqNumber()
						&& topViaHeader.equals(getOriginalRequest()
								.getViaHeaders().getFirst())) {

					transactionMatches = true;
				}

			}

		}

		// JvB: Need to pass the CANCEL to the listener! Retransmitted INVITEs
		// set it to false
		if (transactionMatches) {
			this.setPassToListener();
		}
		return transactionMatches;
	}

	/**
	 * Sets the value of the retransmit timer to the newly supplied timer value.
	 * The retransmit timer is expressed in milliseconds and its default value
	 * is 500ms. This method allows the application to change the transaction
	 * retransmit behavior for different networks. Take the gateway proxy as an
	 * example. The internal intranet is likely to be reatively uncongested and
	 * the endpoints will be relatively close. The external network is the
	 * general Internet. This functionality allows different retransmit times
	 * for either side.
	 * 
	 * @param retransmitTimer -
	 *            the new integer value of the retransmit timer in milliseconds.
	 */
	public void setRetransmitTimer(int retransmitTimer) {

		if (retransmitTimer <= 0)
			throw new IllegalArgumentException(
					"Retransmit timer must be positive!");
		if (this.transactionTimerStarted)
			throw new IllegalStateException(
					"Transaction timer is already started");
		BASE_TIMER_INTERVAL = retransmitTimer;
		T4 = 5000 / BASE_TIMER_INTERVAL;

		T2 = 4000 / BASE_TIMER_INTERVAL;
		TIMER_I = T4;

		TIMER_K = T4;

		TIMER_D = 32000 / BASE_TIMER_INTERVAL;

	}

	/**
	 * Close the encapsulated channel.
	 */
	public void close() {
		this.encapsulatedChannel.close();
		if (sipStack.isLoggingEnabled())
			sipStack.logWriter.logDebug("Closing " + this.encapsulatedChannel);

	}

	public boolean isSecure() {
		return encapsulatedChannel.isSecure();
	}

	public MessageProcessor getMessageProcessor() {
		return this.encapsulatedChannel.getMessageProcessor();
	}

	/**
	 * Set the application data pointer. This is un-interpreted by the stack.
	 * This is provided as a conveniant way of keeping book-keeping data for
	 * applications. Note that null clears the application data pointer
	 * (releases it).
	 * 
	 * @param applicationData --
	 *            application data pointer to set. null clears the applicationd
	 *            data pointer.
	 * 
	 */

	public void setApplicationData(Object applicationData) {
		this.applicationData = applicationData;
	}

	/**
	 * Get the application data associated with this transaction.
	 * 
	 * @return stored application data.
	 */
	public Object getApplicationData() {
		return this.applicationData;
	}

	/**
	 * Set the encapsuated channel. The peer inet address and port are set equal
	 * to the message channel.
	 */
	public void setEncapsulatedChannel(MessageChannel messageChannel) {
		this.encapsulatedChannel = messageChannel;
		this.peerInetAddress = messageChannel.getPeerInetAddress();
		this.peerPort = messageChannel.getPeerPort();
	}

	/**
	 * Return the SipProvider for which the transaction is assigned.
	 * 
	 * @return the SipProvider for the transaction.
	 */
	public SipProviderImpl getSipProvider() {

		return this.getMessageProcessor().getListeningPoint().getProvider();
	}

	/**
	 * Raise an IO Exception event - this is used for reporting asynchronous IO
	 * Exceptions that are attributable to this transaction.
	 * 
	 */
	public void raiseIOExceptionEvent() {
		setState(TransactionState.TERMINATED);
		String host = getPeerAddress();
		int port = getPeerPort();
		String transport = getTransport();
		IOExceptionEvent exceptionEvent = new IOExceptionEvent(this, host,
				port, transport);
		getSipProvider().handleEvent(exceptionEvent, this);
	}

	/**
	 * A given tx can process only a single outstanding event at a time. This
	 * semaphore gaurds re-entrancy to the transaction.
	 * 
	 */
	public boolean acquireSem() {
		boolean retval = false;
		try {
			if (sipStack.getLogWriter().isLoggingEnabled()) {
				sipStack.getLogWriter().logDebug("acquireSem [[[[" + this);
				sipStack.getLogWriter().logStackTrace();
			}
			retval = this.semaphore.tryAcquire(1000, TimeUnit.MILLISECONDS);
			if ( sipStack.isLoggingEnabled())
				sipStack.getLogWriter().logDebug(
					"acquireSem() returning : " + retval);
			return retval;
		} catch (Exception ex) {
			sipStack.logWriter.logError("Unexpected exception acquiring sem",
					ex);
			InternalErrorHandler.handleException(ex);
			return false;
		} finally {
			this.isSemaphoreAquired = retval;
		}

	}

	/**
	 * Release the transaction semaphore.
	 * 
	 */
	public void releaseSem() {
		try {

			this.toListener = false;
			this.semRelease();

		} catch (Exception ex) {
			sipStack.logWriter.logError("Unexpected exception releasing sem",
					ex);

		}

	}

	protected void semRelease() {
		try {
			if (sipStack.getLogWriter().isLoggingEnabled()) {
				sipStack.getLogWriter().logDebug("semRelease ]]]]" + this);
				sipStack.getLogWriter().logStackTrace();
			}
			this.isSemaphoreAquired = false;
			this.semaphore.release();

		} catch (Exception ex) {
			sipStack.logWriter.logError("Unexpected exception releasing sem",
					ex);

		}
	}

	/**
	 * Set true to pass the request up to the listener. False otherwise.
	 * 
	 */

	public boolean passToListener() {
		return toListener;
	}

	/**
	 * Set the passToListener flag to true.
	 */
	public void setPassToListener() {
		if (sipStack.logWriter.isLoggingEnabled()) {
			sipStack.logWriter.logDebug("setPassToListener()");
		}
		this.toListener = true;

	}

	/**
	 * Flag to test if the terminated event is delivered.
	 * 
	 * @return
	 */
	protected synchronized boolean testAndSetTransactionTerminatedEvent() {
		boolean retval = !this.terminatedEventDelivered;
		this.terminatedEventDelivered = true;
		return retval;
	}

	/**
	 * Start the timer that runs the transaction state machine.
	 * 
	 */

	protected abstract void startTransactionTimer();

	/**
	 * Tests a message to see if it is part of this transaction.
	 * 
	 * @return True if the message is part of this transaction, false if not.
	 */
	public abstract boolean isMessagePartOfTransaction(SIPMessage messageToTest);

	/**
	 * This method is called when this transaction's retransmission timer has
	 * fired.
	 */
	protected abstract void fireRetransmissionTimer();

	/**
	 * This method is called when this transaction's timeout timer has fired.
	 */
	protected abstract void fireTimeoutTimer();

}
